import { listStyle, listItemStyle, activeItemStyle } from './defaultStyle'

interface MenuItem {
  id: number | string
  label: string
  data?: any
  hasUnderline?: boolean
}

interface ContextMenuStyle {
  listStyle: Partial<CSSStyleDeclaration>,
  listItemStyle: Partial<CSSStyleDeclaration>
  activeItemStyle: Partial<CSSStyleDeclaration>
}

type MenuFun = (ev: MouseEvent) => MenuItem[]

export class ContextMenu {
  private contextMenuEl: HTMLElement|null = null
  private onContextmenu
  private onUpDocument
  private clickMenu
  private overMenu
  private outMenu
  private styles: ContextMenuStyle
  private menuList: MenuItem[]
  private menuFun: MenuFun|null = null
  constructor(private targetEl: HTMLElement, menuList?: MenuItem[] | MenuFun, private callback?: (id: string, data: any) => void, styles?: ContextMenuStyle) {
    if (!styles || (!styles.listStyle && !styles.listItemStyle)) {
      this.styles = {
        listStyle,
        listItemStyle,
        activeItemStyle
      }
    } else {
      if (!styles.listStyle) {
        styles.listStyle = listStyle
      }
      if (!styles.listItemStyle) {
        styles.listItemStyle = listItemStyle
      }
      if (!styles.activeItemStyle) {
        styles.activeItemStyle = activeItemStyle
      }
      this.styles = styles
    }
    if (typeof menuList === 'function') {
      this.menuFun = menuList
      this.menuList = []
    } else if (Array.isArray(menuList)) {
      this.menuList = menuList
    } else {
      this.menuList = []
    }
    // 初始化ul和li
    this.onContextmenu = this._onContextmenu.bind(this)
    this.onUpDocument = this._onUpDocument.bind(this)
    this.clickMenu = this._clickMenu.bind(this)
    this.overMenu = this._overMenu.bind(this)
    this.outMenu = this._outMenu.bind(this)
    this.init()
  }

  private init() {
    // 初始化 绑定事件
    this.targetEl.addEventListener('contextmenu', this.onContextmenu)
    document.addEventListener('mouseup', this.onUpDocument)
  }

  private _onContextmenu(ev: MouseEvent) {
    ev.preventDefault()
    ev.stopPropagation()
    let menuSize:DOMRect|null = null, rx = 0, ry = 0
    let { x, y } = ev
    const { innerWidth, innerHeight } = window
    if (this.menuFun) {
      this.menuList = this.menuFun(ev)
      this.contextMenuEl && this.updateMenuEl()
    }
    // console.log(this.menuList);
    if (!this.menuList || this.menuList.length === 0) {
      this.contextMenuEl && this.contextMenuEl.style.display === 'block' && (this.contextMenuEl.style.display = 'none')
      return
    }
    
    if (this.contextMenuEl) {
      this.contextMenuEl.style.display = 'block'
    } else {
      this.contextMenuEl = this.getInitMenuEl()
      document.body.appendChild(this.contextMenuEl)
    }

    // 在可视范围内显示，算出坐标
    menuSize = this.contextMenuEl.getBoundingClientRect()
    rx = x + menuSize.width
    ry = y + menuSize.height
    if (rx > innerWidth) {
      x = x - menuSize.width
    }

    if (ry > innerHeight) {
      y = y - menuSize.height
    }
    this.contextMenuEl.style.left = x + 'px'
    this.contextMenuEl.style.top = y + 'px'
  }

  private _onUpDocument(ev: MouseEvent) {
    // 什么时候抬起鼠标，隐藏右键菜单
    if ((ev.button !== 2 || ev.target !== this.targetEl) && this.contextMenuEl) {
      this.contextMenuEl.style.display = 'none'
    }
  }

  private getInitMenuEl() {
    const list = document.createElement('ul')
    list.addEventListener('click', this.clickMenu)
    list.addEventListener('mouseover', this.overMenu)
    list.addEventListener('mouseout', this.outMenu)
    this.loadStyle(list, {...this.styles.listStyle, left: '0px', top: '0px'})
    // 生成menuItem
    this.getMenuItemEls(this.menuList)
      .forEach(item => {
        list.appendChild(item)
      })

    return list
  }

  private getMenuItemEls(menuList: MenuItem[]) {
    let liEl, liEls = []
    for (let item of menuList) {
      liEl = document.createElement('li')
      this.setMenuItem(liEl, { ...this.styles.listItemStyle, borderBottom: item.hasUnderline? this.styles.listItemStyle.borderBottom: '' }, item.id + '', item.label)
      liEls.push(liEl)
    }
    return liEls
  }

  private setMenuItem(liEl: HTMLElement, listItemStyle: Partial<CSSStyleDeclaration>, id: string, label: string) {
    this.loadStyle(liEl, listItemStyle)
    liEl.setAttribute('data-id', id)
    liEl.innerText = label
  }

  private updateMenuEl() {
    const list = this.contextMenuEl as HTMLElement
    const items = list.children
    // items和menuList有三种情况：1. items>menuList  2. items=menuList 3. items<menuList
    // 情况是 1, 2
    for (let len=items.length-1, i=len; i>=0; i--) {
      let item = items[i] as HTMLElement
      let menuItem = this.menuList[i]
      
      if (menuItem) {
        this.setMenuItem(item, { borderBottom: menuItem.hasUnderline? this.styles.listItemStyle.borderBottom: '' }, menuItem.id + '', menuItem.label)
      } else {
        list.removeChild(item)
      }
    }
    // 情况3
    if (items.length < this.menuList.length) {
      this.getMenuItemEls(this.menuList.slice(items.length))
        .forEach(item => {
          list.appendChild(item)
        })
    }
  }

  private _clickMenu(ev: MouseEvent) {
    if (ev.target && ev.target !== this.contextMenuEl && typeof this.callback === 'function') {
      const id = (ev.target as HTMLElement).dataset.id as string
      const items = this.menuList.filter(item => (item.id + '') === id)
      this.callback(id, items? items[0].data: null)
    }
  }

  private _overMenu(ev: MouseEvent) {
    if (ev.target && ev.target !== this.contextMenuEl) {
      (ev.target as HTMLElement).style.backgroundColor = this.styles.activeItemStyle.backgroundColor as string
    }
  }

  private _outMenu(ev: MouseEvent) {
    if (ev.target && ev.target !== this.contextMenuEl) {
      (ev.target as HTMLElement).style.backgroundColor = ''
    }
  }

  private loadStyle(el: HTMLElement, style: Partial<CSSStyleDeclaration>) {
    for (let key in style) {
      if (style.hasOwnProperty(key)) {
        el.style[key] = style[key] as string
      }
    }
  }

  update(menuList: MenuItem[] | MenuFun) {
    if (!this.contextMenuEl) return console.error('右键菜单实例已销毁！')
    if (typeof menuList === 'function') {
      this.menuFun = menuList
      this.menuList = []
    } else {
      this.menuList = menuList
    }
    this.updateMenuEl()
  }

  show(x?: number, y?: number) {
    if (!this.contextMenuEl) return console.error('右键菜单实例已销毁！')
    if (x && y) {
      this.contextMenuEl.style.left = x + 'px'
      this.contextMenuEl.style.top = y + 'px'
    }
    this.contextMenuEl.style.display = 'block'
  }

  hide() {
    if (!this.contextMenuEl) return console.error('右键菜单实例已销毁！')
    this.contextMenuEl.style.display = 'none'
  }

  destroy() {
    if (!this.contextMenuEl) return console.error('右键菜单实例已销毁！')
    this.targetEl.removeEventListener('contextmenu', this.onContextmenu)
    document.removeEventListener('mousedown', this.onUpDocument)
    this.contextMenuEl.addEventListener('click', this.clickMenu)
    this.contextMenuEl.addEventListener('mouseover', this.overMenu)
    this.contextMenuEl.addEventListener('mouseover', this.outMenu)
    document.body.removeChild(this.contextMenuEl)
    this.contextMenuEl = null;
  }

}
